package show.me.the.money.dictionary;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.jackson.map.ObjectMapper;
import org.dom4j.Document;
import org.dom4j.Element;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.web.context.WebApplicationContext;

import show.me.the.money.config.SortComparator;
import show.me.the.money.config.Sortable;
import show.me.the.money.util.AppContextHolder;
import show.me.the.money.util.context.Context;
import show.me.the.money.util.exp.ExpRunner;

@SuppressWarnings("unchecked")
public class TableLayerDictionary extends Dictionary{
	private static Log logger = LogFactory.getLog(TableLayerDictionary.class);
	private CodeRule codeRule;
	private String entryName;
	private String keyField;
	private String textField;
	private String searchField;
	private String searchFieldEx;
	private String sortField;
	private String where = "";
	private boolean queryOnly;
	
	public void setDefineDoc(Document doc) {
		super.setDefineDoc(doc);
		if(doc == null){
			logger.error("init failed,can't get dic [" + id + "]");
			return;
		}
		Element define = defineDoc.getRootElement();
		if(define  == null){
			return;
		}
		
		codeRule = new CodeRule(define.attributeValue("codeRule","20"));
		entryName = define.attributeValue("entry","");
		keyField = define.attributeValue("keyField","id");
		textField = define.attributeValue("textField","text");
		searchField = define.attributeValue("searchField",textField);
		searchFieldEx = define.attributeValue("searchFieldEx");
		sortField = define.attributeValue("sortField",keyField);
		String filter = define.attributeValue("filter","");
		if(filter.length() > 0){
			try {
				ObjectMapper mapper = new ObjectMapper();
				List exp =  mapper.readValue(filter, List.class);
				where  = " where " + ExpRunner.toString(exp,new Context());;
			} 
			catch (Exception e) {
				logger.fatal("get filter error:", e);
			}
		}
		
		queryOnly = Boolean.parseBoolean(define.attributeValue("queryOnly","false"));
		if(entryName.length() == 0){
			logger.error("init failed,can't find [entry] from table dic id [" + id + "]");
			return;
		}
		
		List ls = define.elements();
		String[] propFields = new String[ls.size()];
		StringBuffer props = new StringBuffer();
		for(int i = 0;i < ls.size();i ++){
			Element el = (Element)ls.get(i);
			String fld = el.attributeValue("field");
			propFields[i] = fld;
			props.append(",").append(fld);
		}
		
		WebApplicationContext wac = AppContextHolder.get();
		
		if(wac == null){
			logger.error("init failed,WebApplicationContext is null,table dic id[" + id + "]");
			return;
		}
		SessionFactory sf = (SessionFactory)wac.getBean("mySessionFactory");
		Session ss = sf.openSession();
		try{
			StringBuffer sql = new StringBuffer();
			sql.append("select ").append(keyField).append(",").append(textField)
			.append(props).append(" from ").append(entryName);
			
			sql.toString();
			sql.append(where).append(" order by ").append(sortField);
			Query q = ss.createQuery(sql.toString());
			List records = q.list();
			int rowCount = records.size();
			
			if(rowCount > 16){
				items =new HashMap<String,DictionaryItem>(rowCount);
			}
			for(int i = 0;i < rowCount; i ++){
				Object[] r = (Object[])records.get(i);
				String key = String.valueOf(r[0]);
				String text = String.valueOf(r[1]);
				DictionaryItem dictionaryItem = new DictionaryItem(key,text);
				for(int j = 0; j < propFields.length; j ++){
					if(r[j + 2] != null){
						dictionaryItem.setProperty(propFields[j], String.valueOf(r[j + 2]));
					}
				}
				String parentKey = codeRule.getParentKey(key);
				dictionaryItem.setProperty("parent", parentKey);
				if(!codeRule.isLeaf(key)){
					dictionaryItem.setProperty("folder", "true");
				}

				items.put(key,dictionaryItem);
			}
		}
		catch(Exception e){
			logger.error("init failed,from table dic id [" + id + ", db error \r ", e);
		}
		finally{
			if(ss.isOpen()){
				ss.close();
			}
		}
	}
	
	public List<DictionaryItem> getSlice(String parentKey,int sliceType,String query,int start,int limit) {
		if(StringUtils.isEmpty(query)){
			
			if(!queryOnly){
				switch(sliceType){
					case Dictionary.SLICE_ALL:
						return getAllItems(parentKey,query);
					
					case Dictionary.SLICE_ALL_FOLDER:
						return getAllFolder(parentKey,query);
					
					case Dictionary.SLICE_ALL_LEAF:
						return getAllLeaf(parentKey,query);
						
					case Dictionary.SLICE_CHILD_ALL:
						return getAllChild(parentKey,query);
					
					case Dictionary.SLICE_CHILD_FOLDER:
						return getChildFolder(parentKey,query);
					
					case Dictionary.SLICE_CHILD_LEAF:
						return getChildLeaf(parentKey,query);
				}
			}
			return null;
		}
		else{
			return query(parentKey,query,start,limit);
		}
	}
	

	private List<DictionaryItem> getAllItems(String parentKey,String query){
		List<DictionaryItem> ls = new ArrayList<DictionaryItem>();
		if(StringUtils.isEmpty(parentKey)){
			ls.addAll(items.values());
		}
		else{
			int curLayer = codeRule.indexOfLayer(parentKey);
			if(curLayer == -1  || curLayer == codeRule.getLayerCount() - 1){
				return ls;
			}
			int curLen = codeRule.getLayerLength(curLayer);
			int nextLen = codeRule.getLayerLength(curLayer + 1);
			String hql = "select :keyField form :entry where substring(:keyField,1,:curLen)=:parentKey order by " + sortField;
			
			WebApplicationContext wac = AppContextHolder.get();
			SessionFactory sf = (SessionFactory)wac.getBean("mySessionFactory");
			Session ss = sf.openSession();
			try{
				Query q = ss.createQuery(hql);
				q.setString("keyField", keyField);
				q.setString("entry", entryName);
				q.setInteger("curLen", curLen);
				q.setInteger("nextLen",nextLen);
				q.setString("parentKey", parentKey);
				List<String> records = q.list();
				for(String key : records){
					if(!items.containsKey(key)){
						continue;
					}
					ls.add(items.get(key));
				}
			}
			catch(Exception e){
				logger.fatal("Get dictionary item failed.", e);
			}
			finally{
				if(ss.isOpen()){
					ss.close();
				}
			}
			
		}
		return ls;
	}
	
	private List<DictionaryItem> getAllChild(String parentKey,String query){
		List<DictionaryItem> ls = new ArrayList<DictionaryItem>();
		WebApplicationContext wac = AppContextHolder.get();
		SessionFactory sf = (SessionFactory)wac.getBean("mySessionFactory");
		Session ss = sf.openSession();
		try{
			String hql = "";
			Query q  = null;
			if(StringUtils.isEmpty(parentKey)){
				hql = "select "+ keyField +" from " + entryName + " where length("+ keyField +")=:len order by " +sortField;
				q = ss.createQuery(hql);
				q.setInteger("len", codeRule.getLayerLength(0));
				//q.setString("sortField",sortField);
			}
			else{
				int curLayer = codeRule.indexOfLayer(parentKey);
				if(curLayer == -1  || curLayer == codeRule.getLayerCount() - 1){
					return ls;
				}
				int curLen = codeRule.getLayerLength(curLayer);
				int nextLen = codeRule.getLayerLength(curLayer + 1);
				hql = "select "+ keyField +" from " + entryName + " where substring(" + keyField +",1,:curLen)=:parentKey and length("+ keyField +")=:nextLen order by "+sortField;
				q = ss.createQuery(hql);
				q.setInteger("curLen", curLen);
				q.setInteger("nextLen",nextLen);
				q.setString("parentKey", parentKey);
				q.setInteger("nextLen", nextLen);
				//q.setString("sortField",sortField);
			}
			
			List<String> records = q.list();
			for(String key : records){
				if(!items.containsKey(key)){
					continue;
				}
				ls.add(items.get(key));
			}
		}
		catch(Exception e){
			logger.fatal("Failed to initialize dictionary.", e);
		}
		finally{
			if(ss.isOpen()){
				ss.close();
			}
		}
			

		return ls;
	}
	
	private List<DictionaryItem> getAllLeaf(String parentKey,String query){
		return null;
	}
	
	private List<DictionaryItem> getAllFolder(String parentKey,String query){
		return null;
	}
	
	private List<DictionaryItem> getChildFolder(String parentKey,String query){
		return null;
	}
	
	private List<DictionaryItem> getChildLeaf(String parentKey,String query){
		return null;
	}
	
	public String getText(String key) {
		if(items !=null && items.containsKey(key)){
			return items.get(key).getText();
		}
		return "";
	}

	private List<DictionaryItem> query(String parentKey,String qs,int start,int limit) {
		WebApplicationContext wac = AppContextHolder.get();
		if(wac == null){
			return null;
		}
		SessionFactory sf = (SessionFactory)wac.getBean("mySessionFactory");
		Session ss = sf.openSession();
		List ls = new ArrayList<DictionaryItem>();
		StringBuffer sql = new StringBuffer();
		String curSF = searchField;
		
		if(qs.startsWith("/")){
			curSF = keyField;
			qs = qs.substring(1);
		}
		else if(qs.startsWith(".")){
			curSF = searchFieldEx;
			qs = qs.substring(1);
		}
		try{
			String cnd = StringUtils.isEmpty(where) ? " where " : where + " and ";
			sql.append("select ").append(keyField).append(" from ").append(entryName)
			.append(cnd).append(curSF);
			if(qs.startsWith("=")){
				qs = qs.substring(1);
				sql.append("='").append(qs).append("'");
			}
			else{
				sql.append(" like '").append(qs).append("%'");
			}
			if(!StringUtils.isEmpty(parentKey)){ //in a composeDictionary
				sql.append(" and ").append("substring(").append(searchField).append(",").append(1).append(",").append(parentKey.length())
					.append(")='").append(parentKey).append("'");
			}
			if(where.length() > 0){
				sql.append(" and ").append(where.substring(7)); 
			}
			sql.append(" order by ").append(sortField);
			Query q = ss.createQuery(sql.toString());
			if(limit > 0){
				q.setFirstResult(start);
				q.setMaxResults(limit);
			}
			List records = q.list();
			int rowCount = records.size();
			for(int i = 0;i < rowCount; i ++){
				Object r = records.get(i);
				String key = String.valueOf(r);
				if(!items.containsKey(key)){
					continue;
				}
				ls.add(items.get(key));
			}
			return ls;
		}
		catch(Exception e){
			logger.error("query failed from table dic:", e);
		}
		finally{
			if(ss.isOpen()){
				ss.close();
			}
		}		
		return ls;
	}
	
	public Map<String,DictionaryItem> items(){
		return items;
	}
	
	public List<DictionaryItem> itemsList(){
		Comparator<Sortable> c = new SortComparator();
		List<DictionaryItem> ls = new ArrayList<DictionaryItem>();
		ls.addAll(items.values());
		Collections.sort(ls,c);
		return ls;
	}
	
	public static void reloadDicsByEntryName(String entryName){
		Document defineDoc = Dictionaries.instance().getDefineDoc();
		Element define = defineDoc.getRootElement();
		List<Element> els = define.selectNodes("dic[@class='TableDictionary' or @class='ComposeDictionary']");
		for(Element e : els){
			String id = e.attributeValue("id");
			Document doc = Dictionaries.instance().getDic(id).getDefineDoc();
			if(doc == null){
				continue;
			}
			Element root = doc.getRootElement();
			if(entryName.equals(root.attributeValue("entry"))){
				Dictionaries.instance().reload(id);
				logger.info("dic[" + id + "] reload.");
			}
		}		
	}
	
	public void reloadItem(String key){
		//String hql = loadSql;
	}
	
}

class CodeRule{
	private String codeDefine;
	private int[] layerLens;
	private String[] define;
	private HashMap<Integer,Integer> layersMapping;
	private int count;
	private int maxLen;
	
	public CodeRule(String cd){
		codeDefine = cd;
		define = cd.split(",");
		count = define.length;
		layerLens = new int[count];
		layersMapping = new HashMap<Integer,Integer>();
		int i = 0;
		for(String s : define){
			int len = Integer.parseInt(s);
			maxLen += len;
			if(i == 0){
				layerLens[i] = len;
				layersMapping.put(len,i);
			}
			else{
				layerLens[i] = layerLens[i - 1] + len;
				layersMapping.put(layerLens[i - 1] + len,i);
			}
			i ++;
		}
	}
	
	public int getLayerCount(){
		return count;
	}
	
	public int getLayerLength(int i){
		if(i < count){
			return layerLens[i];
		}
		return 0;
	}
	
	public boolean isLeaf(String key){
		return key.length() == maxLen;
	}
	
	public int indexOfLayer(String key){
		if(StringUtils.isEmpty(key)){
			return -1;
		}
		int keySize = key.length();
		if(layersMapping.containsKey(keySize)){
			return layersMapping.get(keySize);
		}
		return -1;
	}
	
	public String getParentKey(String key){
		if(StringUtils.isEmpty(key)){
			return "";
		}
		int index = indexOfLayer(key);
		if(index < 1){
			return "";
		}
		index--;
		return key.substring(0, layerLens[index]);
	}
	
	public String toString(){
		return codeDefine;
	}
}
